/**
 * 
 * ScrollView.as
 * @Ning
 * 2011-04-26
 * 
 * */

package ui.scroll_view
{
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.events.TimerEvent;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.utils.Timer;
	import flash.utils.getTimer;
	
	import gs.TweenLite;
	import gs.easing.Expo;
	import gs.easing.Linear;
	
	import ui.slider.SliderView;
	
	[Event(name="move_to_top",type="ui.scroll_view.ScrollViewEvent")]
	[Event(name="move_to_bottom",type="ui.scroll_view.ScrollViewEvent")]
	[Event(name="move_to_left",type="ui.scroll_view.ScrollViewEvent")]
	[Event(name="move_to_right",type="ui.scroll_view.ScrollViewEvent")]
	[Event(name="stop_move",type="ui.scroll_view.ScrollViewEvent")]
	[Event(name="moving",type="ui.scroll_view.ScrollViewEvent")]
	
	public class ScrollView extends Sprite
	{
		//START.
		
		private var svContainer:Sprite;
		private var bgFade:Sprite;
		private var svMask:DisplayObject;
		
		private var hScrollIndicator:SliderView;
		private var vScrollIndicator:SliderView;
		private var transMask:Sprite;
		
		//for scroll.
		private var startPoint:Point;
		private var startOffset:Point;
		private var lastOffset:Point;
		private var moveSpeed:Point;
		private var updateTimer:Timer;
		
		private var _pageWidth:Number;
		private var _pageHeight:Number;
		
		private var alwayShowHScroll:Boolean;
		private var alwayShowVScroll:Boolean;
		
		private var donotMoveCount:uint;
		
		protected var containerPositionChanged:Boolean;
		
		private const UPDATE_DURATION:Number = 30;
		private const FLIP_DURATION:Number = 0.8;
		private const BOUNCES_DURATION:Number = 0.5;
		private const CHECK_MOVE_COUNT:uint = 3;
		
		public var contentRect:Rectangle;
		public var pagingEnabled:Boolean = false;
		public var bounces:Boolean = true;
		
		public var showHorizontalScrollIndicator:Boolean = true;
		public var showVerticalScrollIndicator:Boolean = true;
		
		public var lockDirectionV:Boolean = false;
		public var lockDirectionH:Boolean = false;
		
		//CONSTRUCTOR.
		public function ScrollView()
		{
			initialize();
		}
		
		// initialize.
		private function initialize():void
		{
			donotMoveCount = CHECK_MOVE_COUNT;
			
			svContainer = getChildByName("container") as Sprite;
			bgFade = getChildByName("background") as Sprite;
			svMask = getChildByName("msk");
			svContainer.mask = svMask;

			hScrollIndicator = getChildByName("hScroll") as SliderView;
			vScrollIndicator = getChildByName("vScroll") as SliderView;
			hScrollIndicator.hideSlider();
			vScrollIndicator.hideSlider();
			
			contentRect = new Rectangle;
			moveSpeed = new Point;
			lastOffset = contentOffset;
			
			updateTimer = new Timer(UPDATE_DURATION);
			updateTimer.addEventListener(TimerEvent.TIMER,updatePosition);
			updateTimer.start();
			
			addEventListener(MouseEvent.MOUSE_DOWN,mouseDownHandler);
			addEventListener(MouseEvent.MOUSE_UP,mouseUpHandler);
			addEventListener(MouseEvent.ROLL_OUT,mouseUpHandler);
			addEventListener(MouseEvent.MOUSE_WHEEL,mouseWheelHandler);
		}
		
		private var _disableDrag:Boolean = false;
		public function set disableDrag(disable:Boolean):void
		{
			if(disable == this.disableDrag) return;
			this._disableDrag = disable;
			
			if(this.disableDrag)
			{
				removeEventListener(MouseEvent.MOUSE_DOWN,mouseDownHandler);
				removeEventListener(MouseEvent.MOUSE_UP,mouseUpHandler);
				removeEventListener(MouseEvent.ROLL_OUT,mouseUpHandler);
				removeEventListener(MouseEvent.MOUSE_WHEEL,mouseWheelHandler);
				
			}else{
				addEventListener(MouseEvent.MOUSE_DOWN,mouseDownHandler);
				addEventListener(MouseEvent.MOUSE_UP,mouseUpHandler);
				addEventListener(MouseEvent.ROLL_OUT,mouseUpHandler);
				addEventListener(MouseEvent.MOUSE_WHEEL,mouseWheelHandler);
			}
		}
		
		public function get disableDrag():Boolean
		{
			return this._disableDrag;
		}
		
		// on mouse wheel.
		private function mouseWheelHandler(evt:MouseEvent):void
		{
			if(this.height >= contentRect.height || lockDirectionV) 
				return;

			var offset:Number = (evt.delta * -1 / 3) * this.height / 3;
			setContentOffset(new Point(contentRect.x,contentRect.y + offset)); 
		}
		
		// on mouse down.
		private function mouseDownHandler(evt:MouseEvent):void
		{
			TweenLite.killTweensOf(contentRect);

			startPoint = new Point(this.mouseX,this.mouseY);
			startOffset = contentRect.topLeft.clone();
			if(contentRect.topLeft.x > contentRect.width - this.width)
				startOffset.x = 2 * contentRect.topLeft.x - (contentRect.width - this.width);
			if(contentRect.topLeft.x < 0)
				startOffset.x = 2 * contentRect.topLeft.x;
			if(contentRect.topLeft.y > contentRect.height - this.height)
				startOffset.y = 2 * contentRect.topLeft.y - (contentRect.height - this.height);
			if(contentRect.topLeft.y < 0)
				startOffset.y = 2 * contentRect.topLeft.y;
			
			if(!this.hasEventListener(MouseEvent.MOUSE_MOVE))
				addEventListener(MouseEvent.MOUSE_MOVE,mouseMoveHandler);
		}
		
		// on mouse move. means start drag.
		private function mouseMoveHandler(evt:MouseEvent):void
		{
			if(transMask == null)
			{
				transMask = new Sprite;
				transMask.graphics.beginFill(0xFF9900,0);
				transMask.graphics.drawRect(0,0,100,100);
				transMask.graphics.endFill();
				transMask.width = width;
				transMask.height = height;
				
				addChild(transMask);
			}
			
			if(showHorizontalScrollIndicator && !lockDirectionH && hScrollIndicator.sliderBarIsHidden && contentRect.width > this.width) 
				hScrollIndicator.showSlider(false);
			if(showVerticalScrollIndicator && !lockDirectionV && vScrollIndicator.sliderBarIsHidden && contentRect.height > this.height)
				vScrollIndicator.showSlider(false);
			
			var currentPoint:Point = new Point(this.mouseX,this.mouseY);
			var sx:Number = startPoint.x - currentPoint.x + startOffset.x;
			if(sx < 0) 
				sx  = 0.5 * sx;
			if(sx > contentRect.width - this.width)
				sx = 0.5 * sx + 0.5 * (contentRect.width - this.width);
			var sy:Number = startPoint.y - currentPoint.y + startOffset.y;
			if(sy < 0) 
				sy  = 0.5 * sy;
			if(sy > contentRect.height - this.height)
				sy = 0.5 * sy + 0.5 * (contentRect.height - this.height);
			
			contentRect.x = sx;
			contentRect.y = sy;
		}
		
		//on mouse up.
		private function mouseUpHandler(evt:MouseEvent):void
		{
			removeEventListener(MouseEvent.MOUSE_MOVE,mouseMoveHandler);

			if(transMask == null)
				return;
			removeChild(transMask);
			this.transMask = null;
			
			if(showHorizontalScrollIndicator && !hScrollIndicator.sliderBarIsHidden && !alwayShowHorizontalScrollIndicator) 
				hScrollIndicator.hideSlider(true);
			if(showVerticalScrollIndicator && !vScrollIndicator.sliderBarIsHidden && !alwayShowVerticalScrollIndicator)
				vScrollIndicator.hideSlider(true);
			
			TweenLite.killTweensOf(contentRect);
			
			var speedH:Number = moveSpeed.x * 1000;
			var speedV:Number = moveSpeed.y * 1000;
			calculate(speedH,"x","width");
			calculate(speedV,"y","height");
		}
		
		// animated.
		private function calculate(speed:Number,propPos:String,propSize:String):void
		{
			var objProp:Object = new Object;
			if(bounces)
			{
				objProp["overwrite"] = false;
				if(contentOffset[propPos] < 0)
				{
					objProp[propPos] = 0;
					objProp["ease"] = Expo.easeOut;
					TweenLite.to(contentRect,BOUNCES_DURATION,objProp);	
					return;
				}
				if(contentOffset[propPos] > contentRect[propSize] - this[propSize])
				{
					objProp[propPos] = contentRect[propSize] - this[propSize];
					objProp["ease"] = Expo.easeOut;
					TweenLite.to(contentRect,BOUNCES_DURATION,objProp);	
					return;
				}
			}
			
			var acc:Number =  -1 * (speed / FLIP_DURATION);
			var s:Number = contentOffset[propPos] + FLIP_DURATION * speed + 0.5 * acc * Math.pow(FLIP_DURATION,2);
			s = Math.min(contentRect[propSize] - this[propSize],s);
			s = Math.max(0,s);
			if(pagingEnabled)
			{
				var pageIndex:int = Math.round(s / this.pageSize[propPos]);
				s = pageIndex * this.pageSize[propPos];
				s = Math.min(contentRect[propSize] - this[propSize],s);
			}
			objProp[propPos] = s;
			TweenLite.to(contentRect,FLIP_DURATION,objProp);
		}
		
		// update position.
		protected  function updatePosition(evt:Event):void
		{
			if(lastOffset.x == contentOffset.x &&
				lastOffset.y == contentOffset.y)
			{
				moveSpeed.x = 0;
				moveSpeed.y = 0;
				containerPositionChanged = false;
				donotMoveCount += donotMoveCount < CHECK_MOVE_COUNT ? 1 : 0;

				if(donotMoveCount >= CHECK_MOVE_COUNT)
					dispatchEvent(new ScrollViewEvent(ScrollViewEvent.STOP_MOVE,true));
				
				return;
			}
			donotMoveCount = 0;			
			containerPositionChanged = true;
			
			var maxH:Number = contentRect.width - this.width;
			var maxV:Number = contentRect.height - this.height;
			if(!bounces)
			{
				contentRect.x = Math.min(maxH,contentRect.x);
				contentRect.x = Math.max(contentRect.x,0);
				contentRect.y = Math.min(maxV,contentRect.y);
				contentRect.y = Math.max(contentRect.y,0);
			}
			
			// updae h direction.
			if(width < contentRect.width && !lockDirectionH)
			{
				svContainer.x = 0 - contentRect.x;
				
			}else{
				contentRect.x = 0;
				svContainer.x = 0;
			}
			
			// update v direction.
			if(height < contentRect.height && !lockDirectionV)
			{
				svContainer.y = 0 - contentRect.y;
			}else{
				contentRect.y = 0;
				svContainer.y = 0;
			}
			
			hScrollIndicator.update(contentRect.x);
			vScrollIndicator.update(contentRect.y); 
			
			// dispatch event.
			if(!lockDirectionH && contentRect.width > this.width && lastOffset.x != contentOffset.x)
			{
				if(contentOffset.x == 0)
					dispatchEvent(new ScrollViewEvent(ScrollViewEvent.MOVE_TO_LEFT,true));
				if(contentOffset.x == contentRect.width - this.width)
					dispatchEvent(new ScrollViewEvent(ScrollViewEvent.MOVE_TO_RIGHT,true));
			}
			
			if(!lockDirectionV && contentRect.height > this.height && lastOffset.y != contentOffset.y)
			{
				if(contentOffset.y == 0)
					dispatchEvent(new ScrollViewEvent(ScrollViewEvent.MOVE_TO_TOP,true));
				if(contentOffset.y == contentRect.height - this.height)
					dispatchEvent(new ScrollViewEvent(ScrollViewEvent.MOVE_TO_BOTTOM,true));
			}
			
			// update speed.
			moveSpeed.x = (contentOffset.x - lastOffset.x) / UPDATE_DURATION;
			moveSpeed.y = (contentOffset.y - lastOffset.y) / UPDATE_DURATION;
			lastOffset = contentOffset.clone();
			
			dispatchEvent(new ScrollViewEvent(ScrollViewEvent.MOVING,true));
		}
		
		// set background color.
		public function setBackgroundColor(rgb:Number = 0xFFFFFF,alpha:Number = 1.0):void
		{
			while(bgFade.numChildren > 0)
				bgFade.removeChildAt(0);
			
			bgFade.graphics.clear();
			bgFade.graphics.beginFill(rgb,alpha);
			bgFade.graphics.drawRect(0,0,100,100);
			bgFade.graphics.endFill();
			
			bgFade.width = svMask.width;
			bgFade.height = svMask.height;
		}

		public function get moving():Boolean
		{
			return donotMoveCount < CHECK_MOVE_COUNT;
		}
		
		// set page size.
		public function set pageSize(size:Point):void
		{
			_pageWidth = size.x;
			_pageHeight = size.y;
		}
		
		public function get pageSize():Point
		{
			var size:Point = new Point;
			size.x = isNaN(_pageWidth) || _pageWidth == 0 ? this.width : _pageWidth;
			size.y = isNaN(_pageHeight) || _pageHeight == 0 ? this.height : _pageHeight;
			
			return size;
		}
		
		// set content offset.
		public function setContentOffset(offset:Point,animated:Boolean = false):void
		{

			TweenLite.killTweensOf(contentRect);
			
			var maxH:Number = contentRect.width - this.width;
			var maxV:Number = contentRect.height - this.height;

			offset.x = Math.min(maxH,offset.x);
			offset.x = Math.max(offset.x,0);
			offset.y = Math.min(maxV,offset.y);
			offset.y = Math.max(offset.y,0);
			
			if(!animated)
			{
				contentOffset = offset;
				return;
			}
			
			TweenLite.to(contentRect,1.0,{x:offset.x,y:offset.y,ease:Expo.easeInOut});
		}
		
		// set content offset .
		public function set contentOffset(offset:Point):void
		{
			contentRect.x = offset.x;
			contentRect.y = offset.y;
		}
		
		// get content offset.
		public function get contentOffset():Point
		{
			return contentRect.topLeft;
		}
				
		// get content size.
		public function setContentSize(width:Number,height:Number):void
		{
			contentRect.width = width;
			contentRect.height = height;
			
			hScrollIndicator.setSize(contentRect.width,this.width);
			vScrollIndicator.setSize(contentRect.height,this.height);
		}
		
		
		public function set alwayShowHorizontalScrollIndicator(alway:Boolean):void
		{
			alwayShowHScroll = alway;

			if(alway) 
			{
				hScrollIndicator.showSlider();
			}else{
				if(transMask != null && !hScrollIndicator.sliderBarIsHidden)
					hScrollIndicator.hideSlider(true);
			}
		}
		
		public function get alwayShowHorizontalScrollIndicator():Boolean
		{
			return alwayShowHScroll;	
		}
		
		public function set alwayShowVerticalScrollIndicator(alway:Boolean):void
		{
			alwayShowVScroll = alway;
			if(alway) 
			{
				vScrollIndicator.showSlider();
			}else{
				if(transMask != null && !vScrollIndicator.sliderBarIsHidden)
					vScrollIndicator.hideSlider(true);
			}
		}
		
		public function get alwayShowVerticalScrollIndicator():Boolean
		{
			return alwayShowVScroll;			
		}
		
		/**
		 *  overide Method defined in flash.display.Sprite.
		 * */
		// number of subviews.
		public function get numContentChildren():int
		{
			return svContainer.numChildren;
		}
		
		// contains sub view.
		public function contentContains(child:DisplayObject):Boolean
		{
			return svContainer.contains(child);
		}
		
		// add child.
		public function contentAddChild(child:DisplayObject):DisplayObject
		{
			return svContainer.addChild(child);
		}
		
		// remove child.
		public function contentAddChildAt(child:DisplayObject, index:int):DisplayObject
		{
			return svContainer.addChildAt(child,index);
		}
		
		public function contentRemoveChild(child:DisplayObject):DisplayObject
		{
			return svContainer.removeChild(child);
		}

		public function contentRemoveChildAt(index:int):DisplayObject
		{
			return svContainer.removeChildAt(index);
		}
		
		public function get contentNumChildren():Number
		{
			return svContainer.numChildren;
		}
		
		// get child.
		public function contentGetChildAt(index:int):DisplayObject
		{
			return svContainer.getChildAt(index);
		}
		
		public function contentGetChildByName(name:String):DisplayObject
		{
			return svContainer.getChildByName(name);
		}
		
		public function contentGetChildIndex(child:DisplayObject):int
		{
			return svContainer.getChildIndex(child);
		}
		
		//swap child.
		public function contentSwapChildren(child1:DisplayObject, child2:DisplayObject):void
		{
			svContainer.swapChildren(child1,child2);
		}
		
		public function contentSwapChildrenAt(index1:int, index2:int):void
		{
			svContainer.swapChildrenAt(index1,index2);
		}
		
		// set & get size.
		public override function get width():Number
		{
			return bgFade.width;
		}
		
		public override function set width(value:Number):void
		{
			bgFade.width = value;	
			svMask.width = value;
		}
		
		public override function get height():Number
		{
			return bgFade.height;	
		}
		
		public override function set height(value:Number):void
		{
			bgFade.height = value;	
			svMask.height = value;
		}
		
		//END.
	}
}